1 /*
2 * Copyright (c) 2000, 2010, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26 package sun.security.jgss.krb5;
27
28 import org.ietf.jgss.*;
29 import sun.security.jgss.*;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.io.IOException;
33 import java.io.ByteArrayOutputStream;
34 import sun.security.krb5.Confounder;
35
36 /**
37 * This class represents a token emitted by the GSSContext.wrap()
38 * call. It is a MessageToken except that it also contains plaintext
39 * or encrypted data at the end. A wrapToken has certain other rules
40 * that are peculiar to it and different from a MICToken, which is
41 * another type of MessageToken. All data in a WrapToken is prepended
42 * by a random counfounder of 8 bytes. All data in a WrapToken is
43 * also padded with one to eight bytes where all bytes are equal in
44 * value to the number of bytes being padded. Thus, all application
45 * data is replaced by (confounder || data || padding).
46 *
47 * @author Mayank Upadhyay
48 */
49 class WrapToken extends MessageToken {
50 /**
51 * The size of the random confounder used in a WrapToken.
52 */
53 static final int CONFOUNDER_SIZE = 8;
54
55 /*
56 * The padding used with a WrapToken. All data is padded to the
57 * next multiple of 8 bytes, even if its length is already
58 * multiple of 8.
59 * Use this table as a quick way to obtain padding bytes by
60 * indexing it with the number of padding bytes required.
61 */
62 static final byte[][] pads = {
63 null, // No, no one escapes padding
64 {0x01},
65 {0x02, 0x02},
66 {0x03, 0x03, 0x03},
67 {0x04, 0x04, 0x04, 0x04},
68 {0x05, 0x05, 0x05, 0x05, 0x05},
69 {0x06, 0x06, 0x06, 0x06, 0x06, 0x06},
70 {0x07, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07},
71 {0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08}
72 };
73
74 /*
75 * A token may come in either in an InputStream or as a
76 * byte[]. Store a reference to it in either case and process
77 * it's data only later when getData() is called and
78 * decryption/copying is needed to be done. Note that JCE can
79 * decrypt both from a byte[] and from an InputStream.
80 */
81 private boolean readTokenFromInputStream = true;
82 private InputStream is = null;
83 private byte[] tokenBytes = null;
84 private int tokenOffset = 0;
85 private int tokenLen = 0;
86
87 /*
88 * Application data may come from an InputStream or from a
89 * byte[]. However, it will always be stored and processed as a
90 * byte[] since
91 * (a) the MessageDigest class only accepts a byte[] as input and
92 * (b) It allows writing to an OuputStream via a CipherOutputStream.
93 */
94 private byte[] dataBytes = null;
95 private int dataOffset = 0;
96 private int dataLen = 0;
97
98 // the len of the token data: (confounder || data || padding)
99 private int dataSize = 0;
100
101 // Accessed by CipherHelper
102 byte[] confounder = null;
103 byte[] padding = null;
104
105 private boolean privacy = false;
106
107 /**
108 * Constructs a WrapToken from token bytes obtained from the
109 * peer.
110 * @param context the mechanism context associated with this
111 * token
112 * @param tokenBytes the bytes of the token
113 * @param tokenOffset the offset of the token
114 * @param tokenLen the length of the token
115 * @param prop the MessageProp into which characteristics of the
116 * parsed token will be stored.
117 * @throws GSSException if the token is defective
118 */
119 public WrapToken(Krb5Context context,
120 byte[] tokenBytes, int tokenOffset, int tokenLen,
121 MessageProp prop) throws GSSException {
122
123 // Just parse the MessageToken part first
124 super(Krb5Token.WRAP_ID, context,
125 tokenBytes, tokenOffset, tokenLen, prop);
126
127 this.readTokenFromInputStream = false;
128
129 // Will need the token bytes again when extracting data
130 this.tokenBytes = tokenBytes;
131 this.tokenOffset = tokenOffset;
132 this.tokenLen = tokenLen;
133 this.privacy = prop.getPrivacy();
134 dataSize =
135 getGSSHeader().getMechTokenLength() - getKrb5TokenSize();
136 }
137
138 /**
139 * Constructs a WrapToken from token bytes read on the fly from
140 * an InputStream.
141 * @param context the mechanism context associated with this
142 * token
143 * @param is the InputStream containing the token bytes
144 * @param prop the MessageProp into which characteristics of the
145 * parsed token will be stored.
146 * @throws GSSException if the token is defective or if there is
147 * a problem reading from the InputStream
148 */
149 public WrapToken(Krb5Context context,
150 InputStream is, MessageProp prop)
151 throws GSSException {
152
153 // Just parse the MessageToken part first
154 super(Krb5Token.WRAP_ID, context, is, prop);
155
156 // Will need the token bytes again when extracting data
157 this.is = is;
158 this.privacy = prop.getPrivacy();
159 /*
160 debug("WrapToken Cons: gssHeader.getMechTokenLength=" +
161 getGSSHeader().getMechTokenLength());
162 debug("\n token size="
163 + getTokenSize());
164 */
165
166 dataSize =
167 getGSSHeader().getMechTokenLength() - getTokenSize();
168 // debug("\n dataSize=" + dataSize);
169 // debug("\n");
170 }
171
172 /**
173 * Obtains the application data that was transmitted in this
174 * WrapToken.
175 * @return a byte array containing the application data
176 * @throws GSSException if an error occurs while decrypting any
177 * cipher text and checking for validity
178 */
179 public byte[] getData() throws GSSException {
180
181 byte[] temp = new byte[dataSize];
182 getData(temp, 0);
183
184 // Remove the confounder and the padding
185 byte[] retVal = new byte[dataSize - confounder.length -
186 padding.length];
187 System.arraycopy(temp, 0, retVal, 0, retVal.length);
188
189 return retVal;
190 }
191
192 /**
193 * Obtains the application data that was transmitted in this
194 * WrapToken, writing it into an application provided output
195 * array.
196 * @param dataBuf the output buffer into which the data must be
197 * written
198 * @param dataBufOffset the offset at which to write the data
199 * @return the size of the data written
200 * @throws GSSException if an error occurs while decrypting any
201 * cipher text and checking for validity
202 */
203 public int getData(byte[] dataBuf, int dataBufOffset)
204 throws GSSException {
205
206 if (readTokenFromInputStream)
207 getDataFromStream(dataBuf, dataBufOffset);
208 else
209 getDataFromBuffer(dataBuf, dataBufOffset);
210
211 return (dataSize - confounder.length - padding.length);
212 }
213
214 /**
215 * Helper routine to obtain the application data transmitted in
216 * this WrapToken. It is called if the WrapToken was constructed
217 * with a byte array as input.
218 * @param dataBuf the output buffer into which the data must be
219 * written
220 * @param dataBufOffset the offset at which to write the data
221 * @throws GSSException if an error occurs while decrypting any
222 * cipher text and checking for validity
223 */
224 private void getDataFromBuffer(byte[] dataBuf, int dataBufOffset)
225 throws GSSException {
226
227 GSSHeader gssHeader = getGSSHeader();
228 int dataPos = tokenOffset +
229 gssHeader.getLength() + getTokenSize();
230
231 if (dataPos + dataSize > tokenOffset + tokenLen)
232 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
233 "Insufficient data in "
234 + getTokenName(getTokenId()));
235
236 // debug("WrapToken cons: data is token is [" +
237 // getHexBytes(tokenBytes, tokenOffset, tokenLen) + "]\n");
238
239 confounder = new byte[CONFOUNDER_SIZE];
240
241 // Do decryption if this token was privacy protected.
242
243 if (privacy) {
244 cipherHelper.decryptData(this,
245 tokenBytes, dataPos, dataSize, dataBuf, dataBufOffset);
246 /*
247 debug("\t\tDecrypted data is [" +
248 getHexBytes(confounder) + " " +
249 getHexBytes(dataBuf, dataBufOffset,
250 dataSize - CONFOUNDER_SIZE - padding.length) +
251 getHexBytes(padding) +
252 "]\n");
253 */
254
255 } else {
256
257 // Token data is in cleartext
258 // debug("\t\tNo encryption was performed by peer.\n");
259 System.arraycopy(tokenBytes, dataPos,
260 confounder, 0, CONFOUNDER_SIZE);
261 int padSize = tokenBytes[dataPos + dataSize - 1];
262 if (padSize < 0)
263 padSize = 0;
264 if (padSize > 8)
265 padSize %= 8;
266
267 padding = pads[padSize];
268 // debug("\t\tPadding applied was: " + padSize + "\n");
269
270 System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
271 dataBuf, dataBufOffset, dataSize -
272 CONFOUNDER_SIZE - padSize);
273
274 // byte[] debugbuf = new byte[dataSize - CONFOUNDER_SIZE - padSize];
275 // System.arraycopy(tokenBytes, dataPos + CONFOUNDER_SIZE,
276 // debugbuf, 0, debugbuf.length);
277 // debug("\t\tData is: " + getHexBytes(debugbuf, debugbuf.length));
278 }
279
280 /*
281 * Make sure sign and sequence number are not corrupt
282 */
283
284 if (!verifySignAndSeqNumber(confounder,
285 dataBuf, dataBufOffset,
286 dataSize - CONFOUNDER_SIZE
287 - padding.length,
288 padding))
289 throw new GSSException(GSSException.BAD_MIC, -1,
290 "Corrupt checksum or sequence number in Wrap token");
291 }
292
293 /**
294 * Helper routine to obtain the application data transmitted in
295 * this WrapToken. It is called if the WrapToken was constructed
296 * with an Inputstream.
297 * @param dataBuf the output buffer into which the data must be
298 * written
299 * @param dataBufOffset the offset at which to write the data
300 * @throws GSSException if an error occurs while decrypting any
301 * cipher text and checking for validity
302 */
303 private void getDataFromStream(byte[] dataBuf, int dataBufOffset)
304 throws GSSException {
305
306 GSSHeader gssHeader = getGSSHeader();
307
308 // Don't check the token length. Data will be read on demand from
309 // the InputStream.
310
311 // debug("WrapToken cons: data will be read from InputStream.\n");
312
313 confounder = new byte[CONFOUNDER_SIZE];
314
315 try {
316
317 // Do decryption if this token was privacy protected.
318
319 if (privacy) {
320 cipherHelper.decryptData(this, is, dataSize,
321 dataBuf, dataBufOffset);
322
323 // debug("\t\tDecrypted data is [" +
324 // getHexBytes(confounder) + " " +
325 // getHexBytes(dataBuf, dataBufOffset,
326 // dataSize - CONFOUNDER_SIZE - padding.length) +
327 // getHexBytes(padding) +
328 // "]\n");
329
330 } else {
331
332 // Token data is in cleartext
333 // debug("\t\tNo encryption was performed by peer.\n");
334 readFully(is, confounder);
335
336 if (cipherHelper.isArcFour()) {
337 padding = pads[1];
338 readFully(is, dataBuf, dataBufOffset, dataSize-CONFOUNDER_SIZE-1);
339 } else {
340 // Data is always a multiple of 8 with this GSS Mech
341 // Copy all but last block as they are
342 int numBlocks = (dataSize - CONFOUNDER_SIZE)/8 - 1;
343 int offset = dataBufOffset;
344 for (int i = 0; i < numBlocks; i++) {
345 readFully(is, dataBuf, offset, 8);
346 offset += 8;
347 }
348
349 byte[] finalBlock = new byte[8];
350 readFully(is, finalBlock);
351
352 int padSize = finalBlock[7];
353 padding = pads[padSize];
354
355 // debug("\t\tPadding applied was: " + padSize + "\n");
356 System.arraycopy(finalBlock, 0, dataBuf, offset,
357 finalBlock.length - padSize);
358 }
359 }
360 } catch (IOException e) {
361 throw new GSSException(GSSException.DEFECTIVE_TOKEN, -1,
362 getTokenName(getTokenId())
363 + ": " + e.getMessage());
364 }
365
366 /*
367 * Make sure sign and sequence number are not corrupt
368 */
369
370 if (!verifySignAndSeqNumber(confounder,
371 dataBuf, dataBufOffset,
372 dataSize - CONFOUNDER_SIZE
373 - padding.length,
374 padding))
375 throw new GSSException(GSSException.BAD_MIC, -1,
376 "Corrupt checksum or sequence number in Wrap token");
377 }
378
379
380 /**
381 * Helper routine to pick the right padding for a certain length
382 * of application data. Every application message has some
383 * padding between 1 and 8 bytes.
384 * @param len the length of the application data
385 * @return the padding to be applied
386 */
387 private byte[] getPadding(int len) {
388 int padSize = 0;
389 // For RC4-HMAC, all padding is rounded up to 1 byte.
390 // One byte is needed to say that there is 1 byte of padding.
391 if (cipherHelper.isArcFour()) {
392 padSize = 1;
393 } else {
394 padSize = len % 8;
395 padSize = 8 - padSize;
396 }
397 return pads[padSize];
398 }
399
400 public WrapToken(Krb5Context context, MessageProp prop,
401 byte[] dataBytes, int dataOffset, int dataLen)
402 throws GSSException {
403
404 super(Krb5Token.WRAP_ID, context);
405
406 confounder = Confounder.bytes(CONFOUNDER_SIZE);
407
408 padding = getPadding(dataLen);
409 dataSize = confounder.length + dataLen + padding.length;
410 this.dataBytes = dataBytes;
411 this.dataOffset = dataOffset;
412 this.dataLen = dataLen;
413
414 /*
415 debug("\nWrapToken cons: data to wrap is [" +
416 getHexBytes(confounder) + " " +
417 getHexBytes(dataBytes, dataOffset, dataLen) + " " +
418 // padding is never null for Wrap
419 getHexBytes(padding) + "]\n");
420 */
421
422 genSignAndSeqNumber(prop,
423 confounder,
424 dataBytes, dataOffset, dataLen,
425 padding);
426
427 /*
428 * If the application decides to ask for privacy when the context
429 * did not negotiate for it, do not provide it. The peer might not
430 * have support for it. The app will realize this with a call to
431 * pop.getPrivacy() after wrap().
432 */
433 if (!context.getConfState())
434 prop.setPrivacy(false);
435
436 privacy = prop.getPrivacy();
437 }
438
439 public void encode(OutputStream os) throws IOException, GSSException {
440
441 super.encode(os);
442
443 // debug("Writing data: [");
444 if (!privacy) {
445
446 // debug(getHexBytes(confounder, confounder.length));
447 os.write(confounder);
448
449 // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
450 os.write(dataBytes, dataOffset, dataLen);
451
452 // debug(" " + getHexBytes(padding, padding.length));
453 os.write(padding);
454
455 } else {
456
457 cipherHelper.encryptData(this, confounder,
458 dataBytes, dataOffset, dataLen, padding, os);
459 }
460 // debug("]\n");
461 }
462
463 public byte[] encode() throws IOException, GSSException {
464 // XXX Fine tune this initial size
465 ByteArrayOutputStream bos = new ByteArrayOutputStream(dataSize + 50);
466 encode(bos);
467 return bos.toByteArray();
468 }
469
470 public int encode(byte[] outToken, int offset)
471 throws IOException, GSSException {
472
473 // Token header is small
474 ByteArrayOutputStream bos = new ByteArrayOutputStream();
475 super.encode(bos);
476 byte[] header = bos.toByteArray();
477 System.arraycopy(header, 0, outToken, offset, header.length);
478 offset += header.length;
479
480 // debug("WrapToken.encode: Writing data: [");
481 if (!privacy) {
482
483 // debug(getHexBytes(confounder, confounder.length));
484 System.arraycopy(confounder, 0, outToken, offset,
485 confounder.length);
486 offset += confounder.length;
487
488 // debug(" " + getHexBytes(dataBytes, dataOffset, dataLen));
489 System.arraycopy(dataBytes, dataOffset, outToken, offset,
490 dataLen);
491 offset += dataLen;
492
493 // debug(" " + getHexBytes(padding, padding.length));
494 System.arraycopy(padding, 0, outToken, offset, padding.length);
495
496 } else {
497
498 cipherHelper.encryptData(this, confounder, dataBytes,
499 dataOffset, dataLen, padding, outToken, offset);
500
501 // debug(getHexBytes(outToken, offset, dataSize));
502 }
503
504 // debug("]\n");
505
506 // %%% assume that plaintext length == ciphertext len
507 return (header.length + confounder.length + dataLen + padding.length);
508
509 }
510
511 protected int getKrb5TokenSize() throws GSSException {
512 return (getTokenSize() + dataSize);
513 }
514
515 protected int getSealAlg(boolean conf, int qop) throws GSSException {
516 if (!conf) {
517 return SEAL_ALG_NONE;
518 }
519
520 // ignore QOP
521 return cipherHelper.getSealAlg();
522 }
523
524 // This implementation is way too conservative. And it certainly
525 // doesn't return the maximum limit.
526 static int getSizeLimit(int qop, boolean confReq, int maxTokenSize,
527 CipherHelper ch) throws GSSException {
528 return (GSSHeader.getMaxMechTokenSize(OID, maxTokenSize) -
529 (getTokenSize(ch) + CONFOUNDER_SIZE) - 8); /* safety */
530 }
531
532 }